/*
 * world.c
 *
 *  Created on: June 27, 2011
 *      Authors: Lane Aasen, Nikholas Gaffney, Michael Rosenberger, Dylan Swiggett
 */

#include <stdlib.h>
#include <stddef.h>
#include <stdio.h>
#include "boid.h"
#include "vector3D.h"
#include "boidType.h"

typedef struct Node{
	struct Node* next;
	Boid value;
} Node;

typedef struct {
	Node* root;
	Vector3D gravity;
	double afCoefficient;
	double xMin, xMax, yMin, yMax, zMin, zMax;
} World;

void addBoidToWorld(World* ptrWorld, Boid toAdd) {
	Node* ptrNew = malloc(sizeof(Node));
	ptrNew->value = toAdd;
	ptrNew->next = ptrWorld->root;
	ptrWorld->root = ptrNew;
}

void createBoidAndAddToWorld(World* ptrWorld, Vector3D location, Vector3D velocity, double volume, BoidType* type) {
	addBoidToWorld(ptrWorld, initBoid(location, velocity, volume, type));
}

World initWorld(Vector3D gravity, double afCoefficient, double xMin, double xMax, double yMin, double yMax, double zMin, double zMax) {
	World new;
	new.gravity = gravity;
	new.afCoefficient = afCoefficient;
	new.root = NULL;
	new.xMin = xMin;
	new.xMax = xMax;
	new.yMin = yMin;
	new.yMax = yMax;
	new.zMin = zMin;
	new.zMax = zMax;
	return new;
}

void isolatedUpdateBoid(World* world, Boid* boid) {
	boid->velocity = addVector3D(boid->velocity, world->gravity);
	boid->velocity = scaleVector3D(boid->velocity, (1 - world->afCoefficient));
	boid->location = addVector3D(boid->location, boid->velocity);
	if (boid->location.x > world->xMax) {
		boid->location.x = world->xMax;
		boid->velocity.x = -boid->velocity.x;
	} else if (boid->location.x < world->xMin) {
		boid->location.x = world->xMin;
		boid->velocity.x = -boid->velocity.x;
	}
	if (boid->location.y > world->yMax) {
		boid->location.y = world->yMax;
		boid->velocity.y = -boid->velocity.y;
	} else if (boid->location.y < world->yMin) {
		boid->location.y = world->yMin;
		boid->velocity.y = -boid->velocity.y;
	}
	if (boid->location.z > world->zMax) {
		boid->location.z = world->zMax;
		boid->velocity.z = -boid->velocity.z;
	} else if (boid->location.z < world->zMin) {
		boid->location.z = world->zMin;
		boid->velocity.z = -boid->velocity.z;
	}
}

void attrBoid(Boid* siren, Boid* sailor) {
	double distance = magnitudeVector3D(subtractVector3D(siren->location, sailor->location));
	double strength = (siren->type->attrStrength) * (siren->type->attrRadius / distance);
	sailor->velocity = subtractVector3D(sailor->velocity, scaleVector3D(normalizeVector3D(subtractVector3D(sailor->location, siren->location)), strength));
}

void repelBoid(Boid* siren, Boid* sailor) {
	double distance = magnitudeVector3D(subtractVector3D(siren->location, sailor->location));
	double strength = ((siren->type)->repelStrength) * (siren->type->repelRadius / distance);
	sailor->velocity = addVector3D(sailor->velocity, scaleVector3D(normalizeVector3D(subtractVector3D(sailor->location, siren->location)), strength));
}

void findAndUpdateNeighbors(World* ptrWorld, Boid* ptrBoid) {
	double updateRadius = ptrBoid->type->attrRadius;
	if (ptrBoid->type->repelRadius > updateRadius) updateRadius = ptrBoid->type->repelRadius; //puts the larger of the two: attrRadius or repelRadius into the updateRadius
	Node* current = ptrWorld->root;
	while (current) { // while current isn't a NULL pointer (which is 0)
		if (ptrBoid != &(current->value)) {
			if (updateRadius >= magnitudeVector3D(subtractVector3D(current->value.location, ptrBoid->location))) {
				attrBoid(ptrBoid, &(current->value));
				repelBoid(ptrBoid, &(current->value));
			}
		}
		current = current->next;
	}
}

void tick(World* ptrWorld) {
	Node* ptrCurrent = ptrWorld->root;
	while (ptrCurrent) { //first, do neighbor-wise updating
		findAndUpdateNeighbors(ptrWorld, &(ptrCurrent->value));
		ptrCurrent = ptrCurrent->next;
	}
	ptrCurrent = ptrWorld->root;
	while (ptrCurrent) { //then, do isolated updating (don't combine with above loop; doing so would change the behavior of this function)
		isolatedUpdateBoid(ptrWorld, &(ptrCurrent->value));
		ptrCurrent = ptrCurrent->next;
	}
}
